/*
-- IOTA Crypto Core
--
-- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
-- discord: pmaxuw#8292
-- https://gitlab.com/iccfpga-rv
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
-- 
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
-- 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
*/

#include <fpga/conversion_fpga.h>
#include "micro-os-plus/diag/trace.h"

#include "debugprintf.h"

#include <fpga/fpga.h>
#include <string.h>
#include "fpga/pow.h"


//extern Conversion* pConversion;

void Pow::init() {
	m_parallel = powReadParallelLevel();

/*	for (int i=0;i<128;i++) {
		trace_printf("%d - %08x\n", i, fpga.pidiverRead(i));
	}
*/
	if (m_parallel > 7)
		return;

	// log2(parallel)
	uint32_t log2 = 0;
	for (int i = 0; i < 32; i++) {
		if ((m_parallel - 1) & (1 << i)) {
			log2 = i;
		}
	}
	m_log2 = log2;

	powCalcTrytesToTritsTable();

	FPGA::pidiverWrite(CMD_WRITE_FLAGS, (1<<6));
}

// write flags
void Pow::powWriteFlags(char flag_start) {
	uint32_t data = 0;

	if (flag_start)
		data |= 0x00000001;

	FPGA::pidiverWrite(CMD_WRITE_FLAGS, data);
}

uint32_t Pow::powReadParallelLevel() {
	return (FPGA::pidiverRead(CMD_READ_FLAGS) & 0x000000f0) >> 4;
}

uint32_t Pow::powReadBinaryNonce() {
	return FPGA::pidiverRead(CMD_READ_NONCE);
}

void Pow::powWriteMinWeightMagnitude(int bits) {
	FPGA::pidiverWrite(CMD_WRITE_MIN_WEIGHT_MAGNITUDE, (1 << bits) - 1);
}

uint32_t Pow::powGetMask() {
	return ((FPGA::pidiverRead(CMD_READ_FLAGS) >> 8) & ((1 << m_parallel) - 1));
}

uint32_t Pow::powGetFlags() {
	return FPGA::pidiverRead(CMD_READ_FLAGS) & 0x0000000f;
}

// socdiver fpga depending alternate mapping
uint8_t Pow::powTritToBits(char trit) {
	switch (trit) {
		case -1:
			return 0x1;
		case 0:
			return 0x3;
		case 1:
			return 0x2;
		default:
			return 0x0;
	}
}

// socdiver fpga depending alternate mapping
uint8_t Pow::powBitsToTrit(uint8_t bits) {
	switch (bits) {
		case 0x1:
			return -1;
		case 0x3:
			return 0;
		case 0x2:
			return 1;
		default:
			return 0;
	}
}

void Pow::powCalcTrytesToTritsTable() {
	Conversion conv;
	const char* TRYTE_CHARS = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	// table calculates all bits for A -> Z
	for (int i = 0; i < 27; i++) {
		char c = TRYTE_CHARS[i];
		tryte_t tryte;
		trit_t trits[3];
		conv.chars_to_trytes(&c, &tryte, 1);
		conv.trytes_to_trits(&tryte, trits, 1);
		uint8_t packed = 0;
		for (int j=0;j<3;j++) {
			packed |= powTritToBits(trits[j]) << (j*2);
		}
		m_tritsLUT[(uint8_t) c] = packed;
//		printf("%c %08x\n", tryte, packed);
	}
}


// write curl mid-state on FPGA and perform curl optionally
void Pow::powCurlSendBlock(const char* trytes, uint8_t docurl, bool isPoW) {
	int shift = 0;
	uint32_t packed = 0;
	uint32_t addr = 0;
	for (int i = 0; i < 81; i++) {
		uint8_t trits;
		// write '9's where the nonce would be
		// last 27 trytes of last block
		if (!docurl && isPoW && i >= 54) {
			trits = m_tritsLUT['9'];
		} else {
			trits = m_tritsLUT[(uint8_t) *trytes++];
		}
		for (int i=0;i<3;i++) {
			packed |= (trits & 0x3) << shift;
			trits >>= 2;
			shift += 2;
			shift %= 32;
			if (!(shift % 32)) {	// 32bit packed
//				trace_printf("%d: %08x\n", addr, packed);
				FPGA::pidiverWrite(CMD_WRITE_DATA + addr, packed);
				addr++;
				packed = 0;
			}
		}
	}
	// write remaining bits
//	trace_printf("%d: %08x\n", addr, packed);
	FPGA::pidiverWrite(CMD_WRITE_DATA + addr, packed);

	if (docurl) {
		FPGA::pidiverWrite(CMD_WRITE_FLAGS, FLAG_CURL_DO_CURL);
	}

	while (!(powGetFlags() & FLAG_CURL_FINISHED)) {
        	asm("nop");
	}
}

// initialize mid-state on FPGA
void Pow::powCurlInitBlock() {
	FPGA::pidiverWrite(CMD_WRITE_FLAGS, FLAG_CURL_RESET);
}


int Pow::powCurlHash(const char* trytes, char* trytes_hash) {
	powCurlInitBlock();
	for (int blocknr = 0; blocknr < 33; blocknr++) {
		powCurlSendBlock(trytes, 1, false);
		trytes += 81;
	}
	uint32_t hash[16];
	memcpy(hash, (void*) FPGA::curlReadDataPtr(), sizeof(hash));

	trit_t trits[243];

	for (int i=0;i<243;i++) {
		int idx = (i * 2) / 32;
		int bitidx = (i * 2) % 32;
		trits[i] = powBitsToTrit((hash[idx] >> bitidx) & 0x3);
	}

	int mwm = 0;
	for (int i=242;i>=0;i--) {
		if (!trits[i])
			mwm++;
		else
			break;
	}
	tryte_t tr[81];
	Conversion conv;
	conv.trits_to_trytes(trits, tr, 243);
	conv.trytes_to_chars(tr, trytes_hash, 81);
	return mwm;
}


// initialize mid-state on FPGA
void Pow::powCurlInit() {
	FPGA::pidiverWrite(CMD_WRITE_FLAGS, FLAG_CURL_RESET);
}

void Pow::doPoW(const char* trytes, char* nonce, int mwm) {
	powCurlInitBlock();
	for (int blocknr = 0; blocknr < 33; blocknr++) {
		powCurlSendBlock(trytes, (blocknr != 32) ? 1 : 0, true);
		trytes += 81;
	}

	powWriteMinWeightMagnitude(mwm);
	powWriteFlags(1);

	while (!(powGetFlags() & FLAG_FOUND)) {
		asm("nop");
	}

	uint32_t binaryNonce = powReadBinaryNonce() - 2; // -2 because of pipelining for speed on FPGA
	uint32_t mask = powGetMask();
//	debugPrintf("%d %d\n", binaryNonce, mask);
	int found_bit = 0;
	for (int i = 0; i < (int) m_parallel; i++) {
		if (mask & (1 << i)) {
			found_bit = i;
			break;
		}
	}

	const int8_t sig[24] = { 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, -1, 1, 1, -1, -1, 1, -1, 1, 1, 0, 0, 0, 0, 0 };
	memset(m_nonceTrits, 0, NONCE_TRINARY_SIZE);

	// copy signatur
	for (int j = 0; j < 24; j++) {
		m_nonceTrits[j] = sig[j];
	}

	// assemble nonce
	for (int j = 0; j <= (int) m_log2; j++) {
		m_nonceTrits[24 + j] = (found_bit & (1 << j)) ? -1 : 1;
	}

	// insert nonce counter
	for (int i = 0; i < 32; i++) {
		m_nonceTrits[NONCE_TRINARY_SIZE - 32 + i] = (binaryNonce & (1<<i)) ? -1 : 1;
	}

	tryte_t nonce_trytes[27];

	Conversion conv;
	conv.trits_to_trytes(m_nonceTrits, nonce_trytes, 81);
	conv.trytes_to_chars(nonce_trytes, nonce, 27);

}

